import { Ref } from "vue";
import { fixOffsetXY, getRelativeTarget, getXYRelativeToTarget, off, on, RelativeTarget } from "/@/utils/dom";

type DragElement = HTMLElement | SVGElement;
interface MoveData<T, K> {
  dx: number;
  dy: number;
  offsetX: number;
  offsetY: number;
  startData: StartData<T>;
  startResult: K;
}
interface EndData<T, K> {
  dx: number;
  dy: number;
  isClick: boolean;
  startData: StartData<T>;
  startResult: K;
}

export interface UseDragOptions<T, K> {
  unStopPropagation?: boolean;
  start?: (startData: StartData<T>, e: MouseEvent) => K;
  move?: (moveData: MoveData<T, K>, e: MouseEvent) => void;
  end?: (endData: EndData<T, K>, e: MouseEvent) => void;
  //在哪个元素上 绑定drag系列事件，不传则默认是document
  element?: DragElement;
  //若有值则start move end返回的坐标都是相对于该元素（目前只支持相对于HTML元素）
  //否则返回的坐标都是相对于element元素
  relativeElement?: HTMLElement;
  getRelativeElement?: () => HTMLElement;
}

export type DragStart<T extends any[]> = (e: MouseEvent, ...args: T) => void;

interface StartData<T> {
  x: number;
  y: number;
  timeStamp: number;
  relativeTarget?: RelativeTarget;
  //mousedown或touchstart事件的额外参数
  args: T;
}

function getOffsetXY<T>(startData: StartData<T>, e: MouseEvent) {
  let offsetXY: { offsetX: number; offsetY: number };
  if (startData.relativeTarget) {
    offsetXY = getXYRelativeToTarget(startData.relativeTarget, e);
  } else {
    offsetXY = fixOffsetXY(e);
  }
  return offsetXY;
}

//TODO: 支持移动端 touchstart touchmove touchend
export function useDrag<T extends any[], K = void>({
  unStopPropagation,
  start,
  move,
  end,
  element,
  relativeElement,
  getRelativeElement,
}: UseDragOptions<T, K>) {
  if (!element) {
    element = document.body;
  }
  const dragStart: DragStart<T> = (e: MouseEvent, ...args: T) => {
    //屏蔽右键
    if (e.button === 2) return;
    if (!unStopPropagation) {
      e.stopPropagation();
    }

    const startData: StartData<T> = {
      x: 0,
      y: 0,
      timeStamp: e.timeStamp,
      args,
    };
    if (relativeElement) {
      startData.relativeTarget = getRelativeTarget(relativeElement);
    } else if (getRelativeElement) {
      startData.relativeTarget = getRelativeTarget(getRelativeElement());
    } else if (element instanceof HTMLElement) {
      startData.relativeTarget = getRelativeTarget(element);
    }
    let offsetXY = getOffsetXY(startData, e);
    startData.x = offsetXY.offsetX;
    startData.y = offsetXY.offsetY;
    let startResult: K;
    if (start) {
      startResult = start(startData, e);
    }
    const moveHandler = on(element as DragElement, "mousemove", (e) => {
      const { offsetX, offsetY } = getOffsetXY(startData, e);
      const dx = offsetX - startData.x;
      const dy = offsetY - startData.y;
      move && move({ dx, dy, offsetX, offsetY, startData, startResult }, e);
    });
    const endHandler = on(element as DragElement, "mouseup", (e) => {
      const { offsetX, offsetY } = getOffsetXY(startData, e);
      const dx = offsetX - startData.x;
      const dy = offsetY - startData.y;
      const useTime = e.timeStamp - startData.timeStamp;
      end && end({ dx, dy, startData, isClick: dx < 10 && dy < 10 && useTime < 200, startResult }, e);
      off(element as DragElement, "mousemove", moveHandler);
      off(element as DragElement, "mouseup", endHandler);
    });
  };
  return {
    dragStart,
  };
}
